#pragma once
#include <Graphics/AABB.hpp>
#include <Renderer/Renderer.hpp>
#include <Resource/Shader/ShaderSpecify.hpp>
#include <Math/Math.hpp>
#include <Utility/CacheSet.hpp>
#include <3rdParty/boostsignal2.hpp>

namespace zzz{
template<typename T>
class ImageListRenderer : public Renderer , public GraphicsHelper
{
public:
  ImageListRenderer(CacheSet<Image<T>*, zuint> *imgSet, zuint nimage)
  :imgSet_(imgSet), margin_(5), curleft_(0), posleft_(0), mode_(ILMODE_ORI), nimages_(nimage), curimg_(-1)
  {showMsg_=false;}

  void DrawBox(int x1, int y1, int x2, int y2)
  {
    x1-=2;y1-=2;x2+=2;y2+=2;
    Vector3d p0=UnProject(x1, y1, camera_.zNear_);
    Vector3d p1=UnProject(x1, y2, camera_.zNear_);
    Vector3d p2=UnProject(x2, y2, camera_.zNear_);
    Vector3d p3=UnProject(x2, y1, camera_.zNear_);
    
    ColorDefine::yellow.ApplyGL();
    GLLineWidth::Set(2);
    ZCOLORSHADER->Begin();
    GraphicsHelper::DrawLine(p0,p1);
    GraphicsHelper::DrawLine(p1,p2);
    GraphicsHelper::DrawLine(p2,p3);
    GraphicsHelper::DrawLine(p3,p0);
    Shader::End();
    GLLineWidth::Restore();
  }

  bool Draw()
  {
    image_range_.clear();
    switch(mode_) {
    case ILMODE_ORI: {
        Image<T> *img=imgSet_->Get(curleft_);
        glPixelZoom(1,1);
        int posx=posleft_+margin_, posy=postop_+margin_;
        SetRasterPos(posx,posy);
        DrawImage(*img);
        if (curimg_ == curleft_)
          DrawBox(posx, posy, posx+img->Cols(), posy+img->Rows());
        image_range_.push_back(make_pair(curleft_, AABB2i(Vector2i(posx, posy), Vector2i(posx+img->Cols(), posy+img->Rows()))));
      }
      break;
    case ILMODE_SINGLE: {
        int imgheight=height_-margin_-margin_;
        int imgwidth=width_-margin_-margin_;
        Image<T> *img=imgSet_->Get(curleft_);
        //decide ratio
        float zoomratio=Min(float(imgheight)/float(img->Rows()),float(imgwidth)/float(img->Cols()));
        glPixelZoom(zoomratio,zoomratio);
        int posx=(width_-img->Cols()*zoomratio)/2, posy=(height_-img->Rows()*zoomratio)/2;
        SetRasterPos(posx,posy);
        DrawImage(*img);
        if (curimg_ == curleft_)
          DrawBox(posx, posy, posx+img->Cols()*zoomratio, posy+img->Rows()*zoomratio);
        image_range_.push_back(make_pair(curleft_, AABB2i(Vector2i(posx, posy), Vector2i(posx+img->Cols()*zoomratio, posy+img->Rows()*zoomratio))));
      }
      break;
    case ILMODE_HCONT:
    case ILMODE_HSTEP: {
        int imgheight=height_-margin_-margin_;
        int pos=posleft_;
        zuint cur=curleft_;
        while(true) {
          Image<T> *img=imgSet_->Get(cur);
          //decide ratio
          float zoomratio=float(imgheight)/float(img->Rows());
          glPixelZoom(zoomratio,zoomratio);
          SetRasterPos(pos,margin_);
          DrawImage(*img);
          if (curimg_ == cur)
            DrawBox(pos, margin_, pos+img->Cols()*zoomratio, margin_+img->Rows()*zoomratio);
          image_range_.push_back(make_pair(cur, AABB2i(Vector2i(pos, margin_), Vector2i(pos+img->Cols()*zoomratio, margin_+img->Rows()*zoomratio))));
          //prepare next
          pos+=img->Cols()*zoomratio;
          pos+=margin_;
          if (pos>width_) break;
          cur++;
          if (cur>=nimages_) break;
        }
      }
      break;
    case ILMODE_VCONT:
    case ILMODE_VSTEP: {
        int imgwidth=width_-margin_-margin_;
        int pos=posleft_;
        zuint cur=curleft_;
        while(true) {
          Image<T> *img=imgSet_->Get(cur);
          //decide ratio
          float zoomratio=float(imgwidth)/float(img->Cols());
          glPixelZoom(zoomratio,zoomratio);
          int posx=margin_, posy=height_-pos-img->Rows()*zoomratio;
          SetRasterPos(posx,posy);
          DrawImage(*img);
          if (curimg_ == cur)
            DrawBox(posx, posy, posx+img->Cols()*zoomratio, posy+img->Rows()*zoomratio);
          image_range_.push_back(make_pair(cur, AABB2i(Vector2i(posx, posy), Vector2i(posx+img->Cols()*zoomratio, posy+img->Rows()*zoomratio))));
          //prepare next
          pos+=img->Rows()*zoomratio;
          pos+=margin_;
          if (pos>height_) break;
          cur++;
          if (cur>=nimages_) break;
        }
      }
      break;
    }
    return true;
  }

  void SetCurrent(zuint i)
  {
    curleft_=i;
    posleft_=0;
  }

  void SetMargin(zuint s)
  {
    margin_=s;
    Redraw();
  }

  typedef enum {ILMODE_ORI, ILMODE_SINGLE, ILMODE_HCONT, ILMODE_VCONT, ILMODE_HSTEP, ILMODE_VSTEP} ILMODE;
  void SetMode(ILMODE mode)
  {
    mode_=mode;
    switch(mode_) {
    case ILMODE_ORI:
      postop_=0;
    case ILMODE_SINGLE:
    case ILMODE_HSTEP:
    case ILMODE_VSTEP:
      posleft_=0;
      break;
    case ILMODE_HCONT:
    case ILMODE_VCONT:
      break;
    }
    Redraw();
  }

  void OnMouseWheel(unsigned int nFlags, int zDelta, int x,int y)
  {
    switch(mode_) {
    case ILMODE_VCONT:
    case ILMODE_HCONT:
      posleft_+=Sign(zDelta)*60;
      Normalize();
      break;
    case ILMODE_ORI:
    case ILMODE_SINGLE:
    case ILMODE_HSTEP:
    case ILMODE_VSTEP: {
        int oldleft=curleft_;
        curleft_-=Sign(zDelta);
        curleft_=Clamp<int>(0,curleft_,nimages_-1);
        if (oldleft!=curleft_) {
          posleft_=0;
          postop_=0;
        }
      }
      break;
    }
    Redraw();
  }

  void OnKeyDown(unsigned int nChar, unsigned int nRepCnt, unsigned int nFlags)
  {
    switch(nChar) {
    case ZZZKEY_F1: SetMode(ILMODE_ORI); break;
    case ZZZKEY_F2: SetMode(ILMODE_SINGLE); break;
    case ZZZKEY_F3: SetMode(ILMODE_HCONT); break;
    case ZZZKEY_F4: SetMode(ILMODE_HSTEP); break;
    case ZZZKEY_F5: SetMode(ILMODE_VCONT); break;
    case ZZZKEY_F6: SetMode(ILMODE_VSTEP); break;
    case ZZZKEY_PAGEDOWN:
      if (curleft_<(int)nimages_-1) {
        curleft_++;
        posleft_=0;
      }
      break;
    case ZZZKEY_PAGEUP:
      if (curleft_>0) {
        curleft_--;
        posleft_=0;
      }
      break;
    case ZZZKEY_HOME:
      if (curleft_>0) {
        curleft_=0;
        posleft_=0;
      }
      break;
    case ZZZKEY_END:
      if (curleft_<(int)nimages_-1) {
        curleft_=nimages_-1;
        posleft_=0;
      }
      break;
    case ZZZKEY_UP:
    case ZZZKEY_LEFT:
      switch(mode_) {
      case ILMODE_HCONT:
      case ILMODE_VCONT:
        posleft_+=60;
        Normalize();
        break;
      case ILMODE_ORI:
        postop_=0;
      case ILMODE_SINGLE:
      case ILMODE_HSTEP:
      case ILMODE_VSTEP:
        if (curleft_>0) {
          curleft_--;
          posleft_=0;
        }
        break;
      }
      break;
    case ZZZKEY_DOWN:
    case ZZZKEY_RIGHT:
      switch(mode_) {
      case ILMODE_HCONT:
      case ILMODE_VCONT:
        posleft_-=60;
        Normalize();
        break;
      case ILMODE_ORI:
        postop_=0;
      case ILMODE_SINGLE:
      case ILMODE_HSTEP:
      case ILMODE_VSTEP:
        if (curleft_<(int)nimages_-1) {
          curleft_++;
          posleft_=0;
        }
        break;
      }
      break;
    }
    Redraw();
  }

  void OnMouseMove(unsigned int nFlags,int x,int y)
  {
    const int drag_margin=200;
    if (!CheckBit(nFlags, ZZZFLAG_LMOUSE)) return;
    if (Abs(x-lastx_)> 2 || Abs(y-lasty_)>2)
      mouse_moved_=true;
    switch(mode_) {
    case ILMODE_ORI:
      posleft_+=x-lastx_;
      lastx_=x;
      postop_-=y-lasty_;
      lasty_=y;
      {
        Image<T> *img=imgSet_->Get(curleft_);
        int dragtopmost=Min<int>(0,height_-int(img->Rows())-margin_);
        int dragleftmost=Min<int>(0,width_-int(img->Cols())-margin_);
        postop_=Clamp<int>(dragtopmost, postop_, 0);
        posleft_=Clamp<int>(dragleftmost, posleft_, 0);
      }
      Redraw();
      break;
    case ILMODE_HCONT:
      posleft_+=x-lastx_;
      Normalize();
      lastx_=x;
      Redraw();
      break;
    case ILMODE_HSTEP:
      posleft_+=x-lastx_;
      {
        int imgheight=height_-margin_-margin_;
        Image<T> *img=imgSet_->Get(curleft_);
        float zoomratio=float(imgheight)/float(img->Rows());
        int dragleftmost=Min<int>(0,-zoomratio*img->Cols()-margin_+width_);
        if (Within<int>(dragleftmost,posleft_,0)) lastx_=x;
        else if (Within<int>(0,posleft_,drag_margin)) posleft_=0;
        else if (Within<int>(dragleftmost-drag_margin,posleft_,dragleftmost)) posleft_=dragleftmost;
        else if (posleft_<0) {
          curleft_++;
          curleft_=Clamp<int>(0,curleft_,int(nimages_)-1);
          lastx_=x;
          posleft_=0;
        } else {
          curleft_--;
          curleft_=Clamp<int>(0,curleft_,int(nimages_)-1);
          lastx_=x;
          posleft_=0;
        }
      }
      Redraw();
      break;
    case ILMODE_VCONT:
      posleft_+=y-lasty_;
      Normalize();
      lasty_=y;
      Redraw();
      break;
    case ILMODE_VSTEP:
      posleft_+=y-lasty_;
      {
        int imgwidth=width_-margin_-margin_;
        Image<T> *img=imgSet_->Get(curleft_);
        float zoomratio=float(imgwidth)/float(img->Cols());
        int dragleftmost=Min<int>(0,-zoomratio*img->Rows()-margin_+height_);
        if (Within<int>(dragleftmost,posleft_,0)) lasty_=y;
        else if (Within<int>(0,posleft_,drag_margin)) posleft_=0;
        else if (Within<int>(dragleftmost-drag_margin,posleft_,dragleftmost)) posleft_=dragleftmost;
        else if (posleft_<0) {
          curleft_++;
          curleft_=Clamp<int>(0,curleft_,int(nimages_)-1);
          lasty_=y;
          posleft_=0;
        } else {
          curleft_--;
          curleft_=Clamp<int>(0,curleft_,int(nimages_)-1);
          lasty_=y;
          posleft_=0;
        }
      }
      Redraw();
      break;
    }
  }

  void OnLButtonDown(unsigned int nFlags,int x,int y)
  {
    lastx_=x;
    lasty_=y;
    mouse_moved_=false;
  }

  void OnLButtonUp(unsigned int nFlags,int x,int y)
  {
    if (!mouse_moved_) {
      Vector2i pos(x, height_-y-1);
      for (zuint i=0; i<image_range_.size(); i++) {
        if (image_range_[i].second.IsInside(pos)) {
//          signal_select_(image_range_[i].first);
          curimg_=image_range_[i].first;
          Redraw();
          break;
        }
      }
    }
  }

//  boost::signals2::signal<void(zuint)> signal_select_;
protected:
  zuint curimg_;
private:
  void Normalize()
  {
    switch(mode_) {
    case ILMODE_HCONT:
    case ILMODE_HSTEP:
      {
        int imgheight=height_-margin_-margin_;
        int pos=posleft_;
        zuint cur=curleft_;
        while(true) {
          if (cur==0) break;
          if (pos<0) break;
          Image<T> *img=imgSet_->Get(cur-1);
          float zoomratio=float(imgheight)/float(img->Rows());
          pos-=margin_;
          pos-=img->Cols()*zoomratio;
          cur--;
        }
        while(true) {
          Image<T> *img=imgSet_->Get(cur);
          float zoomratio=float(imgheight)/float(img->Rows());
          pos+=img->Cols()*zoomratio;
          if (pos>0) {
            pos-=img->Cols()*zoomratio;
            break;
          }
          pos+=margin_;
          cur++;
          if (cur>=nimages_-1) break;
        }
        if (cur==0 && pos>0){cur=0;pos=0;}
        if (cur>=nimages_-1){cur=nimages_-1;pos=0;}
        posleft_=pos;
        curleft_=cur;
      }
      break;
    case ILMODE_VCONT:
    case ILMODE_VSTEP:
      {
        int imgwidth=width_-margin_-margin_;
        int pos=posleft_;
        zuint cur=curleft_;
        while(true) {
          if (cur==0) break;
          if (pos<0) break;
          Image<T> *img=imgSet_->Get(cur-1);
          float zoomratio=float(imgwidth)/float(img->Cols());
          pos-=margin_;
          pos-=img->Rows()*zoomratio;
          cur--;
        }
        while(true) {
          Image<T> *img=imgSet_->Get(cur);
          float zoomratio=float(imgwidth)/float(img->Cols());
          pos+=img->Rows()*zoomratio;
          if (pos>0) {
            pos-=img->Rows()*zoomratio;
            break;
          }
          pos+=margin_;
          cur++;
          if (cur>=nimages_-1) break;
        }
        if (cur==0 && pos>0){cur=0;pos=0;}
        if (cur>=nimages_-1){cur=nimages_-1;pos=0;}
        posleft_=pos;
        curleft_=cur;
      }
      break;
    }
  }
  CacheSet<Image<T>*, zuint> *imgSet_;
  int margin_;
  int curleft_;
  int posleft_;
  int postop_;
  ILMODE mode_;

  int lastx_, lasty_;
  zuint nimages_;
  vector<pair<zuint, AABB<2,int> > > image_range_;
  bool mouse_moved_;
};
}